﻿using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Runtime.Session;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Mt.Site.Application.Authorization.Permissions;
using Mt.Site.Application.Authorization.Permissions.Dto;
using Mt.Site.Application.Authorization.Roles.Dto;
using Mt.Site.Core.Authorization;
using Mt.Site.Core.Base.Authorization.Roles;
using Mt.Site.Core.Base.Authorization.Users;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Mt.Site.Application.Authorization.Roles
{
    /// <summary>
    /// Application service that is used by 'role management' page.
    /// </summary>
    [AbpAuthorize(AppPermissions.Pages_Administration_Roles)]
    public class RoleAppService : MtSiteAppServiceBase, IRoleAppService
    {
        private readonly RoleManager _roleManager;
        private readonly IPermissionAppService _permissionAppService;
        public RoleAppService(
            RoleManager roleManager,
            IPermissionAppService permissionAppService)
        {
            _roleManager = roleManager;
            _permissionAppService = permissionAppService;
        }

        [HttpPost]
        public async Task<ListResultDto<RoleListDto>> GetRoles(GetRolesInput input)
        {
            var query = _roleManager.Roles;

            List<string> permissionFilterList = input.Permissions == null ? new List<string>() : input.Permissions.Where(p => !string.IsNullOrEmpty(p)).ToList();
            if (input.MenuPermissions != null)
            {
                permissionFilterList.AddRange(input.MenuPermissions.Where(p => !string.IsNullOrEmpty(p)));
            }

            foreach (var permission in permissionFilterList)
            {
                query = query.Where(r => r.Permissions.Any(rp => rp.Name == permission && rp.IsGranted));
            }

            var roles = await query
                .OrderByDescending(item=>item.Order)
                .ThenBy(item=>item.CreationTime)
                .ToListAsync();

            return new ListResultDto<RoleListDto>(ObjectMapper.Map<List<RoleListDto>>(roles));
        }

        public async Task<GetRoleForEditOutput> GetRoleForEdit(NullableIdDto input)
        {
            var permissions = await _permissionAppService.GetAllPermissionsOrderByDisplayNameAsync();

            List<FlatMenuPermissionDto> menuPermissions;
            if (AbpSession.TenantId != null)
            {
                menuPermissions = await _permissionAppService.GetAllMenuPermissionsAsync();
            }
            else
            {
                menuPermissions = new List<FlatMenuPermissionDto>();
            }

            if (input.Id.HasValue)
            {
                var role = await _roleManager.GetRoleByIdAsync(input.Id.Value);

                List<string> grantedMenuPermissionNames;
                if (AbpSession.TenantId != null)
                {
                    grantedMenuPermissionNames = (await _roleManager.GetGrantedMenuPermissionsAsync(role)).ToList();
                }
                else
                {
                    grantedMenuPermissionNames = new List<string>();
                }

                return new GetRoleForEditOutput
                {
                    Role = ObjectMapper.Map<RoleEditDto>(role),
                    Permissions = permissions,
                    GrantedPermissionNames = (await _roleManager.GetGrantedPermissionsAsync(role))
                                .Select(p => p.Name).ToList(),
                    MenuPermissions = menuPermissions,
                    GrantedMenuPermissionNames = grantedMenuPermissionNames
                };
            }
            else
            {
                return new GetRoleForEditOutput
                {
                    Role = new RoleEditDto()
                    {
                        Order = await GetMaxOrderNumber()
                    },
                    Permissions = permissions,
                    GrantedPermissionNames = new List<string>(),
                    MenuPermissions = menuPermissions,
                    GrantedMenuPermissionNames = new List<string>()
                };
            }
        }

        public async Task CreateOrUpdateRole(CreateOrUpdateRoleInput input)
        {
            if (input.Role.Id.HasValue)
            {
                await UpdateRoleAsync(input);
            }
            else
            {
                await CreateRoleAsync(input);
            }
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Delete)]
        public async Task DeleteRole(EntityDto input)
        {
            var role = await _roleManager.GetRoleByIdAsync(input.Id);

            var users = await UserManager.GetUsersInRoleAsync(role.Name);
            foreach (var user in users)
            {
                CheckErrors(await UserManager.RemoveFromRoleAsync(user, role.Name));
            }

            CheckErrors(await _roleManager.DeleteAsync(role));
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Edit)]
        protected virtual async Task UpdateRoleAsync(CreateOrUpdateRoleInput input)
        {
            Debug.Assert(input.Role.Id != null, "input.Role.Id should be set.");

            var role = await _roleManager.GetRoleByIdAsync(input.Role.Id.Value);
            role.DisplayName = input.Role.DisplayName;
            role.IsDefault = input.Role.IsDefault;
            role.Order = input.Role.Order;

            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
            await UpdateGrantedMenuPermissionsAsync(role, input.GrantedMenuPermissionNames);
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Create)]
        protected virtual async Task CreateRoleAsync(CreateOrUpdateRoleInput input)
        {
            var role = new Role(AbpSession.TenantId, input.Role.DisplayName) { IsDefault = input.Role.IsDefault };
            role.Order = input.Role.Order;
            CheckErrors(await _roleManager.CreateAsync(role));
            await CurrentUnitOfWork.SaveChangesAsync(); //It's done to get Id of the role.
            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
            await UpdateGrantedMenuPermissionsAsync(role, input.GrantedMenuPermissionNames);
        }

        private async Task UpdateGrantedPermissionsAsync(Role role, List<string> grantedPermissionNames)
        {
            var grantedPermissions = PermissionManager.GetPermissionsFromNamesByValidating(
                grantedPermissionNames, UserManager, AbpSession.GetUserId());

            await _roleManager.SetGrantedPermissionsAsync(role, grantedPermissions);
        }

        private async Task UpdateGrantedMenuPermissionsAsync(Role role, List<Guid> grantedMenuPermissionNames)
        {
            if (AbpSession.TenantId != null)
            {
                var grantedPermissions = PermissionManager.GetMenuPermissionsByValidating(
                    grantedMenuPermissionNames, UserManager, AbpSession.GetUserId());
                await _roleManager.SetGrantedMenuPermissionsAsync(role, grantedPermissions);
            }
        }

        private async Task<int> GetMaxOrderNumber()
        {
            var query = from q in _roleManager.Roles
                        orderby q.Order descending
                        select q;
            var m = await query.FirstOrDefaultAsync();
            if (m != null)
            {
                return m.Order + 1;
            }
            else
            {
                return 1;
            }
        }
    }
}
